package com.hup.minicontroller.common.bluetooth.handler;

import android.os.Handler;
import android.util.Log;

import com.hup.minicontroller.common.bluetooth.framework.BtBaseFunctionHandler;
import com.hup.minicontroller.common.bluetooth.framework.BtChannel;
import com.hup.minicontroller.common.bluetooth.model.BaseBtDataPacket;
import com.hup.minicontroller.common.bluetooth.model.BtLoginRequestPacket;
import com.hup.minicontroller.common.bluetooth.model.BtLoginResponsePacket;

import java.io.IOException;
import java.util.Iterator;

/**
 * 服务端的登陆认证处理器
 *
 * @author hugan
 * @date 2021/5/17
 */
public class BtLoginHandler implements BtBaseFunctionHandler {
    private static final String TAG = BtLoginHandler.class.getSimpleName();

    private final BtLoginCallback btLoginCallback;

    @SuppressWarnings("unchecked")
    public BtLoginHandler(BtLoginCallback btLoginCallback) {
        this.btLoginCallback = btLoginCallback;
        BtDataSplitCodecHandler.INSTANCE.registerType(btLoginCallback.getLoginRequestType());
    }

    @Override
    public void onChannelActive(BtChannel btChannel) {
        btLoginCallback.getHandler().postDelayed(() -> {
            if (btChannel.isLoginValidSuccess()) return;
            Log.w(TAG, "onChannelActive: 客户端登陆认证失败/无认证,主动断开连接, " + btChannel.getRemoteDevice().getName());
            btChannel.disconnect();
        }, btLoginCallback.getLoginValidTimeout());
    }

    /**
     * 添加了登陆认证处理器后,逻辑过程:
     * .客户端连接成功,客户端发送第一条消息必须是 {@link BtLoginRequestPacket}
     * .服务端socket收到消息,到达本方法->进行认证
     * ->认证成功或失败后,本处理器实例都会从pipeline中被移除
     *
     * @return 能调用本处理器方法的, 最后都是返回null, 不管是否认真通过
     */
    @SuppressWarnings("unchecked")
    @Override
    public BaseBtDataPacket onReceived(BtChannel btChannel, BaseBtDataPacket data, Iterator<BtBaseFunctionHandler> it) throws IOException {
        it.remove();//不论结果,都移除本处理器
        Class<? extends BtLoginRequestPacket> loginRequestType = btLoginCallback.getLoginRequestType();
        if (!loginRequestType.isAssignableFrom(data.getClass())) {
            responseErrorAndClose(btChannel, "认证请求体异常");
            return null;
        }
        try {
            BtLoginRequestPacket loginRequestPacket = (BtLoginRequestPacket) data;
            btLoginCallback.doLoginValid(btChannel, loginRequestPacket);
            Log.d(TAG, "onReceived: 认证成功");
            BtLoginResponsePacket response = btLoginCallback.getLoginSuccessResponse(btChannel, loginRequestPacket);
            btChannel.send(response);
            btChannel.onLoginValidResult(true, loginRequestPacket, null);
        } catch (BtLoginException e) {
            responseErrorAndClose(btChannel, e.getMessage());
        } catch (Exception e) {
            responseErrorAndClose(btChannel, e.getMessage());
            //不用外抛异常了,因为是程序错误,且channel都关闭了,直接打印日志
            Log.e(TAG, "onReceived: 服务器异常", e);
        }
        return null;
    }

    private void responseErrorAndClose(BtChannel btChannel, String err) {
        Log.w(TAG, "responseErrorAndClose: 认证失败:" + btChannel.getRemoteDevice().getName() + ", " + err);
        btChannel.send(BtLoginResponsePacket.failure(err));
        btChannel.disconnect();
    }


    public interface BtLoginCallback<T extends BtLoginRequestPacket, R extends BtLoginResponsePacket> {

        /**
         * 客户端连接成功后,n毫秒内没认证通过,则主动断开连接;
         */
        default int getLoginValidTimeout() {
            return 1000;
        }

        /**
         * 子线程handler:用于处理socket连接后,超时未认证功能
         */
        Handler getHandler();

        /**
         * 获取登陆请求的Class,用于注册解码 {@link BtLoginRequestPacket}
         */
        Class<T> getLoginRequestType();

        /**
         * 校验登陆认证请求;
         * .认证成功时无返回
         * .认证错误时抛出异常 {@link BtLoginException}
         */
        void doLoginValid(BtChannel btChannel, T data) throws BtLoginException;

        /**
         * 获取认证成功回复;用于认证成功后向客户端发送回复
         */
        R getLoginSuccessResponse(BtChannel btChannel, T loginReq);
    }


    public static class BtLoginException extends RuntimeException {
        public BtLoginException(String msg) {
            super(msg);
        }
    }

}
